package com.imooc.pan.server.common.aspect;

import com.imooc.pan.server.common.annotation.LoginIgnore;
import com.imooc.pan.server.common.utils.UserIdUtil;
import com.imooc.pan.server.modules.user.constants.UserConstants;
import com.imooc.pan.web.cache.core.constants.CacheConstants;
import com.imooc.pan.web.core.response.R;
import com.imooc.pan.web.core.response.ResponseCode;
import com.imooc.pan.web.core.utils.JwtUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @Description: 统一的登录拦截校验切面实现类
 * @Author: 张广焱
 * @Date: 2024-10-10 06:27
 */
@Component
@Aspect
public class CommonLoginAspect {
    private static final Logger logger = LoggerFactory.getLogger(CommonLoginAspect.class);
    /**
     * 登录认证参数名称
     */
    private static final String LOGIN_AUTH_PARAM_NAME = "authorization";

    /**
     * 请求头登录认证key
     */
    private static final String LOGIN_AUTH_REQUEST_HEADER_NAME = "Authorization";

    /**
     * 切点表达式
     * * com.imooc.pan.server.modules.*.controller..*(..) ；
     * ‘*’ 返回类型为任何类型，
     * com.imooc.pan.server.modules.*.controller 为modules下所有的controller
     * 。。该文件夹及所有的子文件夹
     * ‘*’所有的方法名
     * （..） 参数列表，任何参数 可有可无
     */
    private final static String POINT_CUT = "execution(* com.imooc.pan.server.modules.*.controller..*(..))";

    @Autowired
    private CacheManager cacheManager;

    /**
     * 切点模版方法
     */
    @Pointcut(value = POINT_CUT)
    public void loginAuth() {

    }

    /**
     * @Description: 切点的环绕增强逻辑
     * 1、需要判断需不需要校验登录信息
     * 2、校验登录信息：
     * a、获取token 从请求头或者参数
     * b、从缓存中获取token，进行比对
     * c、解析token
     * d、解析的userId存入线程上下文，供下游使用
     * @Author: 张广焱
     * @Date: 2024-10-10  06:38:00
     */
    @Around("loginAuth()")
    public Object loginAuthAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        if (checkNeedCheckLoginInfo(proceedingJoinPoint)) {
            //登录信息校验流程
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = servletRequestAttributes.getRequest();
            String requestURI = request.getRequestURI();
            logger.info("成功拦截到请求,{}",requestURI);
            if (!checkAndSaveUserId(request)) {
                logger.warn("成功拦截到请求，URI为：{}. 检测到用户未登录，将跳转至登录页面", requestURI);
                return R.fail(ResponseCode.NEED_LOGIN);
            }
            logger.info("成功拦截到请求，URI为：{}，请求通过", requestURI);
        }
        return proceedingJoinPoint.proceed();
    }

    /**
     * @Description:校验token并提取userId
     * @Author: 张广焱
     * @Date: 2024-10-10  06:48:22
     */
    private boolean checkAndSaveUserId(HttpServletRequest request) {
        String accessToken = request.getHeader(LOGIN_AUTH_REQUEST_HEADER_NAME);
        if (StringUtils.isBlank(accessToken)) {
            accessToken = request.getParameter(LOGIN_AUTH_PARAM_NAME);
        }
        if (StringUtils.isBlank(accessToken)) {
            return false;
        }
        Object userId = JwtUtil.analyzeToken(accessToken, UserConstants.LOGIN_USER_ID);
        if (Objects.isNull(userId)) {
            return false;
        }

        Cache cache = cacheManager.getCache(CacheConstants.R_PAN_CACHE_NAME);
        String redisAccessToken = cache.get(UserConstants.USER_LOGIN_PREFIX + userId, String.class);

        if (StringUtils.isBlank(redisAccessToken)) {
            return false;
        }

        if (Objects.equals(accessToken, redisAccessToken)) {
            saveUserId(userId);
            return true;
        }

        return false;

    }

    private void saveUserId(Object userId) {
        UserIdUtil.set(Long.valueOf(String.valueOf(userId)));
    }

    /**
     * 校验是否需要校验登录信息
     *  判断是否加了@LoginIgnore 注解
     * @param proceedingJoinPoint
     * @return true 需要校验登录信息 false 不需要
     */
    private boolean checkNeedCheckLoginInfo(ProceedingJoinPoint proceedingJoinPoint) {
        //从切点里面获取签名
        Signature signature = proceedingJoinPoint.getSignature();
//        将签名转换为 MethodSignature 类型，以便获取方法相关信息。
        MethodSignature methodSignature = (MethodSignature) signature;
//        通过方法签名获取具体的方法对象。
        Method method = methodSignature.getMethod();
//        检查该方法是否有 LoginIgnore 注解。如果没有该注解，返回 true，表示需要检查登录信息；如果有该注解，返回 false，表示不需要检查登录信息。
        return !method.isAnnotationPresent(LoginIgnore.class);
    }
}


